from heap.class_klass import Klass, HeapObject
from insts.inst import Index16Instruction, Index8Instruction, NoOperandInstruction
from jvm.vm import BaseVM
from jthread.frame import JFrame


class NEW(Index16Instruction):
    def __init__(self, opcode):
        super().__init__(opcode)

    def execute(self, frame: JFrame):
        # 获取当前方法所属的类的运行时常量池
        cp = frame.getJMethod().getKlass().getConstantPool()
        # 从常量池中获取类符号引用
        classRef = cp.getConstantValue(self.index)
        # 解析类符号引用对应的类
        klass: Klass = classRef.resolveClass()
        # 如果类尚未进行初始化，则通过BaseVM进行类的初始化
        if not klass.isInit():
            print("revert next pc and init class in new opcode klass :{}".format(klass.getName()))
            frame.revertNextPc()
            BaseVM().initClass(thread=frame.getJThread(), klass=klass)
            return
        # 若类为接口或抽象类，则抛出实例化异常
        if klass.isInterface() or klass.isAbstract():
            raise Exception("java.lang.InstantiationException")
        # 在堆中为类实例对象分配内存空间
        obj = klass.newHeapClass()
        print(klass.getName())
        # 将类实例对象引用压入操作数栈
        frame.getOperandStack().pushRef(obj)


def getArrayKlass(classLoader, index):
    if index == 4:
        return classLoader.loadClass("[Z")
    elif index == 5:
        return classLoader.loadClass("[C")
    elif index == 6:
        return classLoader.loadClass("[F")
    elif index == 7:
        return classLoader.loadClass("[D")
    elif index == 8:
        return classLoader.loadClass("[B")
    elif index == 9:
        return classLoader.loadClass("[S")
    elif index == 10:
        return classLoader.loadClass("[I")
    elif index == 11:
        return classLoader.loadClass("[J")
    else:
        raise Exception("java.lang.UnsupportedClassVersionError")


class NEW_ARRAY(Index8Instruction):

    def execute(self, frame: JFrame):
        stack = frame.getOperandStack()
        count = stack.popInt()
        if count < 0:
            raise Exception("java.lang.NegativeArraySizeException")
        classLoader = frame.getJMethod().getKlass().getClassLoader()
        arrayKlass = getArrayKlass(classLoader, self.index)
        arr = arrayKlass.newArray(count)
        stack.pushRef(arr)


class ANEW_ARRAY(Index16Instruction):

    def execute(self, frame: JFrame):
        cp = frame.getJMethod().getKlass().getConstantPool()
        classRef = cp.getConstantValue(self.index)
        componentClass = classRef.resolveClass()
        stack = frame.getOperandStack()
        count = stack.popInt()
        if count < 0:
            raise Exception("java.lang.NegativeArraySizeException")
        arr = componentClass.arrayClass().newArray(count)
        stack.pushRef(arr)


class ARRAY_LENGTH(NoOperandInstruction):
    def execute(self, frame: JFrame):
        stack = frame.getOperandStack()
        arr: HeapObject = stack.popRef()
        if arr is None:
            raise Exception("java.lang.NullPointerException")
        stack.pushInt(len(arr.bytes()))


def popAndCheckCounts(stack, dimensions):
    arr = []
    for i in range(0, dimensions):
        arr.append(None)
    val = dimensions
    for i in range(0, dimensions):
        val = val - 1
        arr[val] = stack.popInt()
        if arr[val] < 0:
            raise Exception("java.lang.NegativeArraySizeException")
    return arr


def newMultiDimensionalArray(counts, arrayClass):
    count = counts[0]
    arr = arrayClass.newArray(count)
    if len(counts) > 1:
        refs = arr.refs()
        for i in range(0, len(refs)):
            refs[i] = newMultiDimensionalArray(counts[1:], arrayClass.getComponentClass())
    return arr


class MUL_ANEW_ARRAY(Index16Instruction):

    def __init__(self, opcode):
        super().__init__(opcode)
        self.dimensions = 0

    def FetchOperands(self, reader):
        self.index = reader.readUInt16()
        self.dimensions = reader.readUInt16()

    def execute(self, frame: JFrame):
        cp = frame.getJMethod().getKlass().getConstantPool()
        classRef = cp.getConstantValue(self.index)
        arrayClass = classRef.resolveClass()
        stack = frame.getOperandStack()
        counts = popAndCheckCounts(stack, self.dimensions)
        arr = newMultiDimensionalArray(counts, arrayClass)
        stack.pushRef(arr)
